Atskleiskite visą React useEffect kabliuko potencialą šalutinių efektų valdymui. Vadovas apima pagrindines koncepcijas, dažnus šablonus, pažangias technikas ir geriausią praktiką.
React useEffect meistriškumas: išsamus šalutinių efektų valdymo šablonų vadovas
Šiuolaikinės žiniatinklio kūrimo dinamiškame pasaulyje „React“ išsiskiria kaip galinga biblioteka, skirta vartotojo sąsajoms kurti. Jos komponentais pagrįsta architektūra skatina deklaratyvų programavimą, todėl UI kūrimas yra intuityvus ir efektyvus. Tačiau programos retai egzistuoja izoliuotai; jos dažnai turi sąveikauti su išoriniu pasauliu – gauti duomenis, nustatyti prenumeratas, manipuliuoti DOM arba integruoti su trečiųjų šalių bibliotekomis. Šios sąveikos vadinamos "šalutiniais efektais".
Pasirodo useEffect kabliukas, „React“ funkcinių komponentų kertinis akmuo. Pristatytas kartu su „React Hooks“, useEffect suteikia galingą ir elegantišką būdą valdyti šiuos šalutinius efektus, perkeldamas galimybes, anksčiau rastas klasės komponentų gyvavimo ciklo metoduose (pvz., componentDidMount, componentDidUpdate ir componentWillUnmount), tiesiai į funkcinius komponentus. Suprasti ir įvaldyti useEffect reiškia ne tik rašyti švaresnį kodą; tai reiškia kurti našesnes, patikimesnes ir lengviau prižiūrimas „React“ programas.
Šis išsamus vadovas leis jums giliai pasinerti į useEffect, tyrinėjant jo pagrindinius principus, dažnus naudojimo atvejus, pažangius šablonus ir svarbiausią geriausią praktiką. Nesvarbu, ar esate patyręs „React“ kūrėjas, norintis įtvirtinti savo supratimą, ar esate naujokas kabliukų pasaulyje ir trokštate perprasti šią esminę koncepciją, čia rasite vertingų įžvalgų. Aptarsime viską – nuo pagrindinio duomenų gavimo iki sudėtingo priklausomybių valdymo, užtikrindami, kad būsite pasirengę valdyti bet kokį šalutinio efekto scenarijų.
1. useEffect pagrindų supratimas
Iš esmės useEffect leidžia atlikti šalutinius efektus funkciniuose komponentuose. Iš esmės jis nurodo „React“, kad jūsų komponentui reikia kažką padaryti po atvaizdavimo. „React“ tada paleis jūsų "efekto" funkciją, kai bus išvalyti pakeitimai DOM.
Kas yra šalutiniai efektai „React“?
Šalutiniai efektai – tai operacijos, kurios paveikia išorinį pasaulį arba sąveikauja su išorine sistema. „React“ kontekste tai dažnai reiškia:
- Duomenų gavimas: API iškvietimų atlikimas duomenims gauti ar siųsti.
- Prenumeratos: Įvykių klausytojų (pvz., vartotojo įvesties, globalių įvykių), „WebSocket“ jungčių ar realiojo laiko duomenų srautų nustatymas.
- DOM manipuliavimas: Tiesioginė sąveika su naršyklės dokumento objekto modeliu (DOM) (pvz., dokumento pavadinimo keitimas, fokusavimo valdymas, integravimas su ne „React“ bibliotekomis).
- Laikmačiai: Naudojant
setTimeoutarbasetInterval. - Registravimas: Analitikos duomenų siuntimas.
Pagrindinė useEffect sintaksė
useEffect kabliukas priima du argumentus:
- Funkciją, kurioje yra šalutinio efekto logika. Ši funkcija gali pasirinktinai grąžinti valymo funkciją.
- Pasirenkamą priklausomybių masyvą.
import React, { useEffect, useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
// This is the side effect function
console.log('Component rendered or count changed:', count);
// Optional cleanup function
return () => {
console.log('Cleanup for count:', count);
};
}, [count]); // Dependency array
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Priklausomybių masyvas: Valdymo raktas
Antrasis useEffect argumentas – priklausomybių masyvas – yra labai svarbus kontroliuojant, kada efektas vykdomas. „React“ iš naujo paleis efektą tik tuo atveju, jei kuri nors iš reikšmių priklausomybių masyve pasikeitė tarp atvaizdavimų.
-
Nėra priklausomybių masyvo: Efektas vykdomas po kiekvieno komponento atvaizdavimo. Tai retai norima našumo požiūriu kritiniams efektams, pvz., duomenų gavimui, nes tai gali sukelti begalinius ciklus arba nereikalingus pakartotinius vykdymus.
useEffect(() => { // Runs after every render }); -
Tuščias priklausomybių masyvas (
[]): Efektas vykdomas tik vieną kartą po pradinio atvaizdavimo (prijungimo) ir valymo funkcija vykdoma tik vieną kartą prieš komponento atjungimą. Tai idealiai tinka efektams, kurie turėtų įvykti tik vieną kartą, pvz., pradiniam duomenų gavimui arba globalių įvykių klausytojų nustatymui.useEffect(() => { // Runs once on mount console.log('Component mounted!'); return () => { // Runs once on unmount console.log('Component unmounted!'); }; }, []); -
Priklausomybių masyvas su reikšmėmis (
[propA, stateB]): Efektas vykdomas po pradinio atvaizdavimo ir kiekvieną kartą, kai pasikeičia bet kuri masyvo reikšmė. Tai dažniausias ir universaliausias naudojimo atvejis, užtikrinantis, kad jūsų efekto logika būtų sinchronizuota su atitinkamais duomenų pokyčiais.useEffect(() => { // Runs on mount and whenever 'userId' changes fetchUser(userId); }, [userId]);
Valymo funkcija: nutekėjimo ir klaidų prevencija
Daugelis šalutinių efektų reikalauja "valymo" žingsnio. Pavyzdžiui, jei nustatote prenumeratą, turite atšaukti prenumeratą, kai komponentas atjungiamas, kad išvengtumėte atminties nutekėjimo. Jei paleidžiate laikmatį, turite jį išjungti. Valymo funkcija grąžinama iš jūsų useEffect atgalinio iškvietimo.
„React“ paleidžia valymo funkciją prieš vėl paleisdamas efektą (jei pasikeičia priklausomybės) ir prieš komponento atjungimą. Tai užtikrina, kad ištekliai būtų tinkamai atlaisvinti ir būtų sumažintos galimos problemos, pvz., lenktynių sąlygos ar pasenusios uždarymo funkcijos.
useEffect(() => {
const subscription = subscribeToChat(props.chatId);
return () => {
// Cleanup: Unsubscribe when chatId changes or component unmounts
unsubscribeFromChat(subscription);
};
}, [props.chatId]);
2. Dažni useEffect naudojimo atvejai ir šablonai
Panagrinėkime praktinius scenarijus, kuriuose useEffect pasižymi, kartu su geriausia praktika kiekvienam.
2.1. Duomenų gavimas
Duomenų gavimas yra bene dažniausias useEffect naudojimo atvejis. Jūs norite gauti duomenis, kai komponentas prijungiamas, arba kai pasikeičia konkrečios savybės/būsenos reikšmės.
Pagrindinis gavimas prijungus
import React, { useEffect, useState } from 'react';
function UserProfile() {
const [userData, setUserData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUserData = async () => {
try {
const response = await fetch('https://api.example.com/users/1');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUserData(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUserData();
}, []); // Empty array ensures this runs only once on mount
if (loading) return <p>Loading user data...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!userData) return <p>No user data found.</p>;
return (
<div>
<h2>{userData.name}</h2>
<p>Email: {userData.email}</p>
<p>Location: {userData.location}</p>
</div>
);
}
Gavimas su priklausomybėmis
Dažnai gaunami duomenys priklauso nuo dinaminės reikšmės, pvz., vartotojo ID, paieškos užklausos ar puslapio numerio. Kai šios priklausomybės pasikeičia, norite iš naujo gauti duomenis.
import React, { useEffect, useState } from 'react';
function UserPosts({ userId }) {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
if (!userId) { // Handle cases where userId might be undefined initially
setPosts([]);
setLoading(false);
return;
}
const fetchUserPosts = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(`https://api.example.com/users/${userId}/posts`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPosts(data);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchUserPosts();
}, [userId]); // Re-fetch whenever userId changes
if (loading) return <p>Loading posts...</p>;
if (error) return <p>Error: {error.message}</p>;
if (posts.length === 0) return <p>No posts found for this user.</p>;
return (
<div>
<h3>Posts by User {userId}</h3>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
Lenktynių sąlygų valdymas duomenų gavime
Kai priklausomybės keičiasi greitai, galite susidurti su lenktynių sąlygomis, kai senesnė, lėtesnė tinklo užklausa baigiasi po naujesnės, greitesnės, o tai lemia pasenusių duomenų rodymą. Dažnas šablonas, skirtas tai sušvelninti, yra naudoti žymę arba AbortController.
import React, { useEffect, useState } from 'react';
function ProductDetails({ productId }) {
const [product, setProduct] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchProduct = async () => {
setLoading(true);
setError(null);
setProduct(null); // Clear previous product data
try {
const response = await fetch(`https://api.example.com/products/${productId}`, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setProduct(data);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchProduct();
return () => {
// Abort ongoing fetch request if component unmounts or productId changes
controller.abort();
};
}, [productId]);
if (loading) return <p>Loading product details...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!product) return <p>No product found.</p>;
return (
<div>
<h2>{product.name}</h2>
<p>Price: ${product.price}</p>
<p>Description: {product.description}</p>
</div>
);
}
2.2. Įvykių klausytojai ir prenumeratos
Įvykių klausytojų (pvz., klaviatūros įvykių, lango dydžio keitimo) ar išorinių prenumeratų (pvz., „WebSockets“, pokalbių paslaugų) valdymas yra klasikinis šalutinis efektas. Valymo funkcija čia yra gyvybiškai svarbi, kad būtų išvengta atminties nutekėjimo ir užtikrinta, kad įvykių apdorojimo priemonės būtų pašalintos, kai jų nebereikia.
Globalus įvykių klausytojas
import React, { useEffect, useState } from 'react';
function WindowSizeLogger() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener('resize', handleResize);
return () => {
// Clean up the event listener when component unmounts
window.removeEventListener('resize', handleResize);
};
}, []); // Empty array: add/remove listener only once on mount/unmount
return (
<div>
<p>Window Width: {windowSize.width}px</p>
<p>Window Height: {windowSize.height}px</p>
</div>
);
}
Pokalbių paslaugos prenumerata
import React, { useEffect, useState } from 'react';
// Assume chatService is an external module providing subscribe/unsubscribe methods
import { chatService } from './chatService';
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
useEffect(() => {
const handleNewMessage = (message) => {
setMessages((prevMessages) => [...prevMessages, message]);
};
const subscription = chatService.subscribe(roomId, handleNewMessage);
return () => {
chatService.unsubscribe(subscription);
};
}, [roomId]); // Re-subscribe if roomId changes
return (
<div>
<h3>Chat Room: {roomId}</h3>
<div className=\"messages\">
{messages.length === 0 ? (
<p>No messages yet.</p>
) : (
messages.map((msg, index) => (
<p key={index}><strong>{msg.sender}:</strong> {msg.text}</p>
))
)}
</div>
</div>
);
}
2.3. DOM manipuliavimas
Nors „React“ deklaratyvus pobūdis dažnai abstrahuoja tiesioginį DOM manipuliavimą, kartais reikia sąveikauti su neapdorotu DOM, ypač integruojant su trečiųjų šalių bibliotekomis, kurios tikisi tiesioginės prieigos prie DOM.
Dokumento pavadinimo keitimas
import React, { useEffect } from 'react';
function PageTitleUpdater({ title }) {
useEffect(() => {
document.title = `My App | ${title}`;
}, [title]); // Update title whenever 'title' prop changes
return (
<h2>Welcome to the {title} Page!</h2>
);
}
Integracija su trečiosios šalies diagramų biblioteka (pvz., Chart.js)
import React, { useEffect, useRef } from 'react';
import Chart from 'chart.js/auto'; // Assuming Chart.js is installed
function MyChartComponent({ data, labels }) {
const chartRef = useRef(null); // Ref to hold the canvas element
const chartInstance = useRef(null); // Ref to hold the chart instance
useEffect(() => {
if (chartRef.current) {
// Destroy existing chart instance before creating a new one
if (chartInstance.current) {
chartInstance.current.destroy();
}
const ctx = chartRef.current.getContext('2d');
chartInstance.current = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Sales Data',
data: data,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderColor: 'rgba(75, 192, 192, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
}
});
}
return () => {
// Cleanup: Destroy the chart instance on unmount
if (chartInstance.current) {
chartInstance.current.destroy();
}
};
}, [data, labels]); // Re-render chart if data or labels change
return (
<div style={{ width: '600px', height: '400px' }}>
<canvas ref={chartRef}></canvas>
</div>
);
}
2.4. Laikmačiai
setTimeout arba setInterval naudojimas „React“ komponentuose reikalauja kruopštaus valdymo, kad laikmačiai nebeveiktų po to, kai komponentas buvo atjungtas, o tai gali sukelti klaidų ar atminties nutekėjimų.
Paprastas atgalinis laikmatis
import React, { useEffect, useState } from 'react';
function CountdownTimer({ initialSeconds }) {
const [seconds, setSeconds] = useState(initialSeconds);
useEffect(() => {
if (seconds <= 0) return; // Stop timer when it reaches zero
const timerId = setInterval(() => {
setSeconds(prevSeconds => prevSeconds - 1);
}, 1000);
return () => {
// Cleanup: Clear the interval when component unmounts or seconds become 0
clearInterval(timerId);
};
}, [seconds]); // Re-run effect if seconds changes to set up new interval (e.g. if initialSeconds changes)
return (
<div>
<h3>Countdown: {seconds} seconds</h3>
{seconds === 0 && <p>Time's up!</p>}
</div>
);
}
3. Pažangūs useEffect šablonai ir spąstai
Nors useEffect pagrindai yra paprasti, jo įvaldymas apima subtilesnių elgsenų ir dažnų spąstų supratimą.
3.1. Pasenusios uždarymo funkcijos ir pasenusios reikšmės
Dažna problema su useEffect (ir apskritai „JavaScript“ uždarymo funkcijomis) yra pasenusių reikšmių prieiga iš ankstesnio atvaizdavimo. Jei jūsų efekto uždarymo funkcija užfiksuoja būseną ar savybę, kuri pasikeičia, bet jūs neįtraukiate jos į priklausomybių masyvą, efektas ir toliau matys seną reikšmę.
Apsvarstykite šį problemišką pavyzdį:
import React, { useEffect, useState } from 'react';
function StaleClosureExample() {
const [count, setCount] = useState(0);
useEffect(() => {
// This effect wants to log the count after 2 seconds.
// If count changes within these 2 seconds, this will log the OLD count!
const timer = setTimeout(() => {
console.log('Stale Count:', count);
}, 2000);
return () => {
clearTimeout(timer);
};
}, []); // Problem: 'count' is not in dependencies, so it's stale
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Norėdami tai ištaisyti, įsitikinkite, kad visos reikšmės, naudojamos jūsų efekto viduje, kurios gaunamos iš savybių ar būsenos, yra įtrauktos į priklausomybių masyvą:
import React, { useEffect, useState } from 'react';
function FixedClosureExample() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setTimeout(() => {
console.log('Correct Count:', count);
}, 2000);
return () => {
clearTimeout(timer);
};
}, [count]); // Solution: 'count' is now a dependency. Effect re-runs when count changes.
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Tačiau priklausomybių pridėjimas kartais gali sukelti per dažną efekto vykdymą. Tai atveda mus prie kitų šablonų:
Funkcinių atnaujinimų naudojimas būsenai
Atnaujinant būseną, remiantis jos ankstesne reikšme, naudokite funkcinę set- funkcijų atnaujinimo formą. Tai pašalina poreikį įtraukti būsenos kintamąjį į priklausomybių masyvą.
import React, { useEffect, useState } from 'react';
function AutoIncrementer() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(prevCount => prevCount + 1); // Functional update
}, 1000);
return () => clearInterval(interval);
}, []); // 'count' is not a dependency because we use functional update
return <p>Count: {count}</p>;
}
useRef keičiamoms reikšmėms, nesukeliančioms pakartotinio atvaizdavimo
Kartais jums reikia saugoti keičiamą reikšmę, kuri nesukelia pakartotinio atvaizdavimo, bet yra prieinama jūsų efekto viduje. useRef tam puikiai tinka.
import React, { useEffect, useRef, useState } from 'react';
function LatestValueLogger() {
const [count, setCount] = useState(0);
const latestCountRef = useRef(count); // Create a ref
// Keep the ref's current value updated with the latest count
useEffect(() => {
latestCountRef.current = count;
}, [count]);
useEffect(() => {
const interval = setInterval(() => {
// Access the latest count via the ref, avoiding stale closure
console.log('Latest Count:', latestCountRef.current);
}, 2000);
return () => clearInterval(interval);
}, []); // Empty dependency array, as we are not directly using 'count' here
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useCallback ir useMemo stabilioms priklausomybėms
Kai funkcija ar objektas yra jūsų useEffect priklausomybė, tai gali sukelti nereikalingą efekto pakartotinį vykdymą, jei funkcijos/objekto nuoroda keičiasi kiekvieno atvaizdavimo metu (o taip dažniausiai ir būna). useCallback ir useMemo padeda memoizuodami šias reikšmes, suteikdami stabilią nuorodą.
Problemiškas pavyzdys:
import React, { useEffect, useState } from 'react';
function UserSettings() {
const [userId, setUserId] = useState(1);
const [settings, setSettings] = useState({});
const fetchSettings = async () => {
// This function is re-created on every render
console.log('Fetching settings for user:', userId);
const response = await fetch(`https://api.example.com/users/${userId}/settings`);
const data = await response.json();
setSettings(data);
};
useEffect(() => {
fetchSettings();
}, [fetchSettings]); // Problem: fetchSettings changes on every render
return (
<div>
<p>User ID: {userId}</p>
<button onClick={() => setUserId(userId + 1)}>Next User</button>
<pre>{JSON.stringify(settings, null, 2)}</pre>
</div>
);
}
Sprendimas su useCallback:
import React, { useEffect, useState, useCallback } from 'react';
function UserSettingsOptimized() {
const [userId, setUserId] = useState(1);
const [settings, setSettings] = useState({});
const fetchSettings = useCallback(async () => {
console.log('Fetching settings for user:', userId);
const response = await fetch(`https://api.example.com/users/${userId}/settings`);
const data = await response.json();
setSettings(data);
}, [userId]); // fetchSettings only changes when userId changes
useEffect(() => {
fetchSettings();
}, [fetchSettings]); // Now fetchSettings is a stable dependency
return (
<div>
<p>User ID: {userId}</p>
<button onClick={() => setUserId(userId + 1)}>Next User</button>
<pre>{JSON.stringify(settings, null, 2)}</pre>
</div>
);
}
Panašiai, objektams ar masyvams naudokite useMemo, kad sukurtumėte stabilią nuorodą:
import React, { useEffect, useMemo, useState } from 'react';
function ProductList({ categoryId, sortBy }) {
const [products, setProducts] = useState([]);
// Memoize the filter/sort criteria object
const fetchCriteria = useMemo(() => ({
category: categoryId,
sort: sortBy,
}), [categoryId, sortBy]);
useEffect(() => {
// fetch products based on fetchCriteria
console.log('Fetching products with criteria:', fetchCriteria);
// ... API call logic ...
}, [fetchCriteria]); // Effect runs only when categoryId or sortBy changes
return (
<div>
<h3>Products in Category {categoryId} (Sorted by {sortBy})</h3>
<!-- Render product list -->
</div>
);
}
3.2. Begaliniai ciklai
Begalinis ciklas gali atsirasti, jei efektas atnaujina būsenos kintamąjį, kuris taip pat yra jo priklausomybių masyve, ir atnaujinimas visada sukelia pakartotinį atvaizdavimą, kuris vėl suaktyvina efektą. Tai dažna klaida, kai nesate atsargūs su priklausomybėmis.
import React, { useEffect, useState } from 'react';
function InfiniteLoopExample() {
const [data, setData] = useState([]);
useEffect(() => {
// This will cause an infinite loop!
// setData causes a re-render, which re-runs the effect, which calls setData again.
setData([1, 2, 3]);
}, [data]); // 'data' is a dependency, and we're always setting a new array reference
return <p>Data length: {data.length}</p>;
}
Norėdami tai ištaisyti, įsitikinkite, kad jūsų efektas veikia tik tada, kai tikrai reikia, arba naudokite funkcinius atnaujinimus. Jei norite nustatyti duomenis tik vieną kartą prijungus, naudokite tuščią priklausomybių masyvą.
import React, { useEffect, useState } from 'react';
function CorrectDataSetup() {
const [data, setData] = useState([]);
useEffect(() => {
// This runs only once on mount
setData([1, 2, 3]);
}, []); // Empty array prevents re-runs
return <p>Data length: {data.length}</p>;
}
3.3. Našumo optimizavimas su useEffect
Probleminių sričių skaidymas į kelis useEffect kabliukus
Užuot sugrūdus visus šalutinius efektus į vieną didelį useEffect, suskaidykite juos į kelis kabliukus. Kiekvienas useEffect tada gali valdyti savo priklausomybių ir valymo logikos rinkinį. Tai padaro kodą labiau skaitomą, prižiūrimą ir dažnai apsaugo nuo nereikalingų nesusijusių efektų pakartotinio vykdymo.
import React, { useEffect, useState } from 'react';
function UserDashboard({ userId }) {
const [profile, setProfile] = useState(null);
const [activityLog, setActivityLog] = useState([]);
// Effect for fetching user profile (depends only on userId)
useEffect(() => {
const fetchProfile = async () => {
// ... fetch profile data ...
console.log('Fetching profile for', userId);
const response = await fetch(`https://api.example.com/users/${userId}/profile`);
const data = await response.json();
setProfile(data);
};
fetchProfile();
}, [userId]);
// Effect for fetching activity log (also depends on userId, but separate concern)
useEffect(() => {
const fetchActivity = async () => {
// ... fetch activity data ...
console.log('Fetching activity for', userId);
const response = await fetch(`https://api.example.com/users/${userId}/activity`);
const data = await response.json();
setActivityLog(data);
};
fetchActivity();
}, [userId]);
return (
<div>
<h2>User Dashboard: {userId}</h2>
<h3>Profile:</h3>
<pre>{JSON.stringify(profile, null, 2)}</pre>
<h3>Activity Log:</h3>
<pre>{JSON.stringify(activityLog, null, 2)}</pre>
</div>
);
}
3.4. Individualūs kabliukai pakartotiniam naudojimui
Kai pastebite, kad rašote tą pačią useEffect logiką keliuose komponentuose, tai yra stiprus požymis, kad galite ją abstrahuoti į individualų kabliuką. Individualūs kabliukai yra funkcijos, kurios prasideda use ir gali iškviesti kitus kabliukus, todėl jūsų logika yra pakartotinai naudojama ir lengviau testuojama.
Pavyzdys: useFetch individualus kabliukas
import React, { useEffect, useState } from 'react';
// Custom Hook: useFetch.js
function useFetch(url, dependencies = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const signal = abortController.signal;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(err);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortController.abort();
};
}, [url, ...dependencies]); // Re-run if URL or any extra dependency changes
return { data, loading, error };
}
// Component using the custom hook: UserDataDisplay.js
function UserDataDisplay({ userId }) {
const { data: userData, loading, error } = useFetch(
`https://api.example.com/users/${userId}`,
[userId] // Pass userId as a dependency to the custom hook
);
if (loading) return <p>Loading user data...</p>;
if (error) return <p>Error: {error.message}</p>;
if (!userData) return <p>No user data.</p>;
return (
<div>
<h2>{userData.name}</h2>
<p>Email: {userData.email}</p>
</div>
);
}
4. Kada useEffect *nenaudoti*
Nors useEffect yra galingas, jis ne visada yra tinkamiausias įrankis kiekvienam darbui. Netinkamas jo naudojimas gali sukelti nereikalingą sudėtingumą, našumo problemų ar sunkiai derinamos logikos.
4.1. Išvestinei būsenai ar apskaičiuotoms reikšmėms
Jei turite būseną, kurią galima tiesiogiai apskaičiuoti iš kitos esamos būsenos ar savybių, jums nereikia useEffect. Apskaičiuokite ją tiesiogiai atvaizdavimo metu.
Bloga praktika:
function ProductCalculator({ price, quantity }) {
const [total, setTotal] = useState(0);
useEffect(() => {
setTotal(price * quantity); // Unnecessary effect
}, [price, quantity]);
return <p>Total: ${total.toFixed(2)}</p>;
}
Gera praktika:
function ProductCalculator({ price, quantity }) {
const total = price * quantity; // Computed directly
return <p>Total: ${total.toFixed(2)}</p>;
}
Jei skaičiavimas yra brangus, apsvarstykite useMemo, bet vis tiek ne useEffect.
import React, { useMemo } from 'react';
function ComplexProductCalculator({ items }) {
const memoizedTotal = useMemo(() => {
console.log('Recalculating total...');
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}, [items]);
return <p>Complex Total: ${memoizedTotal.toFixed(2)}</p>;
}
4.2. Savybių ar būsenos pokyčiams, kurie turėtų sukelti vaiko komponentų pakartotinį atvaizdavimą
Pagrindinis būdas perduoti duomenis vaikams ir sukelti jų pakartotinį atvaizdavimą yra per savybes (props). Nenaudokite useEffect tėviniame komponente būsenai atnaujinti, kuri vėliau perduodama kaip savybė, kai pakaktų tiesioginio savybės atnaujinimo.
4.3. Efektams, kuriems nereikia valymo ir kurie yra grynai vizualūs
Jei jūsų šalutinis efektas yra grynai vizualus ir neapima jokių išorinių sistemų, prenumeratų ar laikmačių, ir jam nereikia valymo, jums gali nereikėti useEffect. Paprastiems vizualiniams atnaujinimams ar animacijoms, kurios nepriklauso nuo išorinės būsenos, gali pakakti CSS arba tiesioginio „React“ komponento atvaizdavimo.
Išvada: useEffect įvaldymas patikimoms programoms
useEffect kabliukas yra nepakeičiama dalis kuriant tvirtas ir reaktyvias „React“ programas. Jis elegantiškai sujungia „React“ deklaratyvų UI ir imperatyvų šalutinių efektų pobūdį. Suprasdami jo pagrindinius principus – efekto funkciją, priklausomybių masyvą ir esminį valymo mechanizmą – įgyjate tikslų valdymą, kada ir kaip vykdomi jūsų šalutiniai efektai.
Ištyrėme daugybę šablonų, nuo įprastų duomenų gavimo ir įvykių valdymo iki sudėtingų scenarijų, tokių kaip lenktynių sąlygos ir pasenusios uždarymo funkcijos, valdymo. Taip pat pabrėžėme individualių kabliukų galią abstrahuojant ir pakartotinai naudojant efekto logiką – tai praktika, kuri žymiai pagerina kodo prižiūrimumą ir skaitomumą įvairiuose projektuose ir globaliose komandose.
Norėdami įvaldyti useEffect, prisiminkite šias pagrindines išvadas:
- Nustatykite tikrus šalutinius efektus: Naudokite
useEffectsąveikoms su "išoriniu pasauliu" (API, DOM, prenumeratos, laikmačiai). - Kruopščiai valdykite priklausomybes: Priklausomybių masyvas yra jūsų pagrindinis valdymas. Būkite tikslūs, nuo kokių reikšmių priklauso jūsų efektas, kad išvengtumėte pasenusių uždarymo funkcijų ir nereikalingų pakartotinių vykdymų.
- Prioritetas – valymas: Visada apsvarstykite, ar jūsų efektui reikalingas valymas (pvz., prenumeratos atšaukimas, laikmačių išvalymas, užklausų atšaukimas), kad išvengtumėte atminties nutekėjimo ir užtikrintumėte programos stabilumą.
- Atskirti rūpesčius: Naudokite kelis
useEffectkabliukus skirtingiems, nesusijusiems šalutiniams efektams viename komponente. - Pasinaudokite individualiais kabliukais: Inkapsuliuokite sudėtingą ar pakartotinai naudojamą
useEffectlogiką į individualius kabliukus, kad pagerintumėte moduliškumą ir pakartotinį naudojimą. - Venkite dažnų spąstų: Saugokitės begalinių ciklų ir įsitikinkite, kad nenaudojate
useEffectpaprastai išvestinei būsenai ar tiesioginiam savybių perdavimui.
Taikydami šiuos šablonus ir geriausią praktiką, būsite gerai pasirengę pasitikėdami valdyti šalutinius efektus savo „React“ programose, kurdami aukštos kokybės, našią ir keičiamo dydžio vartotojo patirtį vartotojams visame pasaulyje. Tęskite eksperimentavimą, mokykitės ir kurkite nuostabius dalykus su „React“!